#include <compilation/task/RenamingTask.h>

#include <sstream>
#include <memory>


struct AssignmentMark
{
  std::shared_ptr<int> counter;
  AssignmentMark* parent = nullptr;
  std::string oldName;
  std::string newName;

  void mangleNameForAssignment(compilation::VirtualExpression* expr)
  {
    assert(counter.get());

    oldName = getName(expr);

    std::stringstream ss;
    ss << oldName << "_" << (*counter);

    ++(*counter);

    DEBUG_EXPRESSION_VISITOR
        << "variable " << oldName
        << " renamed as " << ss.str() << std::endl;

    newName = ss.str();
    setNewName(expr);
  }

  void mangleName(compilation::VirtualExpression* expr)
  {
    DEBUG_EXPRESSION_VISITOR
        << "variable " << oldName
        << " renamed as " << newName << std::endl;
    setNewName(expr);
  }

private:

  void setNewName(compilation::VirtualExpression* expr)
  {
    switch (expr->type())
    {
      case compilation::TLocalVariable:
      {
        compilation::LocalVariableExpression* loc =
            compilation::expression_cast<compilation::TLocalVariable>(expr);
        loc->rename(newName);
        break;
      }
      case compilation::TGlobalVariable:
      {
        compilation::GlobalVariableExpression* glo =
            compilation::expression_cast<compilation::TGlobalVariable>(expr);
        glo->rename(newName);
        break;
      }
      default: assert(false);
    }
  }

  std::string const& getName(compilation::VirtualExpression* expr)
  {
    switch (expr->type())
    {
      case compilation::TLocalVariable:
      {
        compilation::LocalVariableExpression* loc =
            compilation::expression_cast<compilation::TLocalVariable>(expr);
        return loc->name();
      }
      case compilation::TGlobalVariable:
      {
        compilation::GlobalVariableExpression* glo =
            compilation::expression_cast<compilation::TGlobalVariable>(expr);
        return glo->name();
      }
      default: assert(false);
    }
  }
};


namespace compilation
{


RenamingAgenda::~RenamingAgenda(void)
{
  for (VirtualInstruction* label: m_markedInstruction)
    label->clearMark();

  for (ExpressionInstruction* expr : m_assignment)
  {
    delete reinterpret_cast< ::AssignmentMark* >(expr->mark);
    expr->clearMark();
  }
}


void RenamingAgenda::markInstructionWith(
    VirtualInstruction &instruction, VirtualTask &task)
{
  if (instruction.hasType<LabelInstruction>() && !instruction.mark)
  {
    m_markedInstruction.push_back(
          instruction_cast<TLabel>(&instruction));

    instruction.mark = reinterpret_cast<void*>(0x1);
  }
}


bool RenamingTask::mergeWith(VirtualTask &source)
{
  DEBUG_EXPRESSION_VISITOR
      << "Try to merge task on "
      << getInstruction().getRegistrationIndex()
      << " and task on "
      << source.getInstruction().getRegistrationIndex()
      << std::endl;

  // TODO

  return false;
}


ExpressionInstruction*
RenamingTask::getLastApparitionPosition(const std::string &name)
{
  auto it = m_lastApparition.find(name);
  if (it != m_lastApparition.end())
    return it->second.first;

  return nullptr;
}

VirtualExpression *RenamingTask::getLastApparitionVariable(const std::string &name)
{
  auto it = m_lastApparition.find(name);
  if (it != m_lastApparition.end())
    return it->second.second;

  return nullptr;
}

void RenamingTask::signalVariableModification(
    const std::string &name, ExpressionInstruction* where, VirtualExpression* what)
{ m_lastApparition[name] = std::make_pair(where,what); }


void RenamingExpressionHandler<TComparison>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  ComparisonExpression* comp = expression_cast<TComparison>(expr);

  if (comp->getFst())
    continuations.handleExpression(
          comp->getFst(), task, continuations, reuse);
  if (comp->getSnd())
    continuations.handleExpression(
          comp->getSnd(), task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TUnaryOperator>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  UnaryOperatorExpression* unar = expression_cast<TUnaryOperator>(expr);

  if (unar->getSubExpression())
    continuations.handleExpression(
          unar->getSubExpression(), task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TBinaryOperator>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  BinaryOperatorExpression* bina = expression_cast<TBinaryOperator>(expr);

  if (bina->getFst())
    continuations.handleExpression(
          bina->getFst(), task, continuations, reuse);
  if (bina->getSnd())
    continuations.handleExpression(
          bina->getSnd(), task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TDereference>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  DereferenceExpression* deref = expression_cast<TDereference>(expr);

  if (deref->getSubExpression())
    continuations.handleExpression(
          deref->getSubExpression(), task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TReference>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  ReferenceExpression* ref = expression_cast<TReference>(expr);

  if (ref->getSubExpression())
    continuations.handleExpression(
          ref->getSubExpression(), task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TCast>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  CastExpression* cast = expression_cast<TCast>(expr);

  if (cast->getSubExpression())
    continuations.handleExpression(
          cast->getSubExpression(), task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TFunctionCall>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  FunctionCallExpression* func = expression_cast<TFunctionCall>(expr);

  for (std::vector<VirtualExpression*>::const_iterator iter =
       func->getArguments().begin();
       iter != func->getArguments().end(); ++iter)
    if (*iter)
      continuations.handleExpression(
            *iter, task, continuations, reuse);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TAssign>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  AssignExpression* assi = expression_cast<TAssign>(expr);

  task.setAsLValue(false);
  continuations.handleExpression(
        assi->getRValue(), task, continuations, reuse);

  task.setAsLValue(true);
  continuations.handleExpression(
        assi->getLValue(), task, continuations, reuse);

  task.setAsLValue(false);

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingExpressionHandler<TLocalVariable>::operator ()(
    VirtualExpression* expr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  LocalVariableExpression* local = expression_cast<TLocalVariable>(expr);

  DEBUG_EXPRESSION_VISITOR
      << "visit local variable "
      << local->name()
      << std::endl;

  if (debug_expression_visitor)
  {
    DEBUG_EXPRESSION_VISITOR
        << "  apparitions:\n";

    auto app = task.getApparitions();
    for (auto const& val : app)
      DEBUG_EXPRESSION_VISITOR
          << "    " << val.first
          << " -> " << val.second.first->getRegistrationIndex()
          << std::endl;
  }

  if (task.isLValue())
  {
    DEBUG_EXPRESSION_VISITOR << "  is LValue\n";

    AssignmentMark* mark = new AssignmentMark;
    task.getInstruction().mark = mark;
    continuations.declareAssignmentMark(
          instruction_cast<TExpression>(&task.getInstruction()));

    ExpressionInstruction* where = task.getLastApparitionPosition(local->name());
    if (where)
    {
      DEBUG_EXPRESSION_VISITOR
          << "  previous apparition found at "
          << where->getRegistrationIndex() << std::endl;

      AssignmentMark& whereMark =
          where->getMarkAs<AssignmentMark>();

      mark->counter = whereMark.counter;
      mark->parent = &whereMark;
    }
    else
    {
      mark->counter = std::shared_ptr<int>(new int(0));
      mark->parent = nullptr;
    }

    task.signalVariableModification(
          local->name(),
          instruction_cast<TExpression>(&task.getInstruction()),
          expr);

    mark->mangleNameForAssignment(local);
    local->scope().addDeclaration(
          local->name(),
          local->scope().getType(local->getLocalIndex()).clone());

    local->scope().renameDeclaration(mark->newName, mark->oldName);

    DEBUG_EXPRESSION_VISITOR
        << "  declare assignment mark at "
        << task.getInstruction().getRegistrationIndex()
        << std::endl;
  }
  else
  {
    DEBUG_EXPRESSION_VISITOR
        << "  is RValue\n";

    ExpressionInstruction* where = task.getLastApparitionPosition(local->name());
    if (where)
    {
      DEBUG_EXPRESSION_VISITOR
          << "  previous apparition found at "
          << where->getRegistrationIndex() << std::endl;

      AssignmentMark& mark = where->getMarkAs<AssignmentMark>();
      mark.mangleName(local);
    }
  }

  base_handler::operator ()(expr,task,continuations,reuse);
}


void RenamingInstructionHandler<TReturn>::operator ()(
    VirtualInstruction* instr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  ReturnInstruction* ret = instruction_cast<TReturn>(instr);

  if (ret->getResult())
    continuations.handleExpression(
          ret->getResult(), task, continuations, reuse);

  base_handler::operator ()(instr,task,continuations,reuse);
}


void RenamingInstructionHandler<TExpression>::operator ()(
    VirtualInstruction* instr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  ExpressionInstruction* expr = instruction_cast<TExpression>(instr);

  DEBUG_EXPRESSION_VISITOR << "visiting expression\n";

  if (expr->getExpression())
  {
    continuations.handleExpression(
          expr->getExpression(), task, continuations, reuse);
  }

  base_handler::operator ()(instr,task,continuations,reuse);
}


void RenamingInstructionHandler<TIf>::operator ()(
    VirtualInstruction* instr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  IfInstruction* ifins = instruction_cast<TIf>(instr);

  if (ifins->getCondition())
  {
    continuations.handleExpression(
          ifins->getCondition(), task, continuations, reuse);
  }

  base_handler::operator ()(instr,task,continuations,reuse);
}


void RenamingInstructionHandler<TGoto>::operator ()(
    VirtualInstruction* instr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  GotoInstruction* gt = instruction_cast<TGoto>(instr);

  if (gt->isContext(GotoInstruction::CLoop) ||
      gt->isContext(GotoInstruction::CBeforeLabel))
  {
    (void) instruction_cast<TLabel>(gt->getSNextInstruction());

    VirtualInstruction* current =
        gt->getSNextInstruction()->getSNextInstruction();
    while (current && (current->type() == TExpression))
    {
      ExpressionInstruction* expr = instruction_cast<TExpression>(current);
      if (expr->getExpression()->type() != TAssign) break;
      AssignExpression* ass = expression_cast<TAssign>(expr->getExpression());

      if (ass->getRValue()->type() == TPhi)
      {
        PhiExpression* phi = expression_cast<TPhi>(ass->getRValue());

        std::pair<bool,bool>& status = continuations.getPhiStatus(phi);

        /**/ if (!status.first)
        {
          LocalVariableExpression* local =
              expression_cast<TLocalVariable>(phi->getFst());

          ExpressionInstruction* prev = task.getLastApparitionPosition(local->name());
          prev->getMarkAs<AssignmentMark>().mangleName(local);

          status.first = true;
        }
        else if (!status.second)
        {
          std::string name;
          if (current->mark)
          {
            AssignmentMark& mark = current->getMarkAs<AssignmentMark>();
            name = mark.oldName;
          }
          else
          {
            VirtualExpression* tmp = ass->getLValue();
            switch (tmp->type())
            {
              case TLocalVariable:
                name = expression_cast<TLocalVariable>(tmp)->name();
                break;
              case TGlobalVariable:
                name = expression_cast<TGlobalVariable>(tmp)->name();
                break;
              default:
                assert(false);
            }
          }

          ExpressionInstruction* prev = task.getLastApparitionPosition(name);

          if (phi->getSnd())
          {
            LocalVariableExpression* var =
                expression_cast<TLocalVariable>(phi->getSnd());

            prev->getMarkAs<AssignmentMark>().mangleName(var);
          }
          else
          {
            VirtualExpression* var = task.getLastApparitionVariable(name)->clone();
            prev->getMarkAs<AssignmentMark>().mangleName(var);
            phi->addReference(var, *gt);
          }

          status.second = true;
        }
        else assert(false);
      }
      else break;

      current = current->getSNextInstruction();
    }
  }

  base_handler::operator ()(instr,task,continuations,reuse);
}


void RenamingInstructionHandler<TEnterBlock>::operator ()(
    VirtualInstruction* instr,
    RenamingTask& task,
    RenamingAgenda& continuations,
    WorkListReusability& reuse)
{
  EnterBlockInstruction* enter = instruction_cast<TEnterBlock>(instr);

  base_handler::operator ()(instr,task,continuations,reuse);
}


} // compilation
